#include "iap.h"

static uint32_t gFlashBuf[GD_SECTOR_SIZE/4] = {0};    // 每页可存储字数

iapfun jump2app; 

//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(uint32_t appxaddr)
{
	if(((*(vuint32_t*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
	{		
		__disable_irq();
		jump2app=(iapfun)*(vuint32_t*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP(*(vuint32_t*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		jump2app();									//跳转到APP.
	}
}

/* 读内部flash两个字节 */
uint16_t fmc_read_halfword(uint32_t addr)
{
	return *(vuint16_t*)addr;
}


/* 向指定地址写入 iap 升级标志 */
void write_iap_flag(uint32_t addr, uint16_t data)
{
	fmc_unlock();  /* 解锁 */
	fmc_flag_clear(FMC_FLAG_BANK0_END | FMC_FLAG_BANK0_PGERR | FMC_FLAG_BANK0_WPERR);  /* 清除标志 */
	fmc_page_erase(addr);  /* 擦除扇区 */
	fmc_halfword_program(addr, data);  /* 写入升级标志: 0x1234 */
	fmc_lock();  /* 上锁 */
	
	if (fmc_read_halfword(addr) != data) {
		printk("write_iap_flag filed!\r\n");
	}
}




//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(uint32_t addr) 
{
    MSR MSP, r0 			//set Main Stack value
    BX r14
}


void iap_softreset(void)
{
	__disable_irq();
	__set_FAULTMASK(1);
	NVIC_SystemReset();  /* CM3/4内核定义的软件复位 */
}

/**
 * @brief 
*/
static void FLASH_WriteNoCheck(uint32_t adr, uint32_t *buf, uint16_t len)   
{
    uint16_t i = 0;
    
    for(i=0; i<len; i++)
    {
        fmc_word_program(adr, buf[i]);
        adr += 4;
    }  
} 

/**
 * @brief 写FLASH
*/
void FLASH_Write(uint32_t adr, uint32_t *buf, uint16_t len)
{
    uint32_t secpos;        // 扇区地址
    uint16_t secoff;        // 扇区内偏移地址(32位字计算)
    uint16_t secremain;     // 扇区内剩余地址(32位字计算)
    uint32_t offaddr;       // 去掉0X08000000后的地址
    uint16_t i;

    if ((adr < GD32_FLASH_BASE_ADR) || (adr >= (GD32_FLASH_BASE_ADR + 1024 * GD32_FLASH_SIZE)))
    {
        return;//非法地址
    }

    fmc_unlock();

    offaddr = adr - GD32_FLASH_BASE_ADR;        // 实际偏移地址.
    secpos = offaddr / GD_SECTOR_SIZE;          // 扇区地址
    secoff = (offaddr % GD_SECTOR_SIZE) / 4;    // 在扇区内的偏移(4个字节为基本单位.)
    secremain = GD_SECTOR_SIZE / 4 - secoff;    // 扇区剩余空间大小   

    if (len <= secremain)
    {
        secremain = len;        // 不大于该扇区范围
    }

    while (1) 
    {	
        FLASH_Read(secpos * GD_SECTOR_SIZE + GD32_FLASH_BASE_ADR, gFlashBuf, GD_SECTOR_SIZE / 4);   // 读出整个扇区的内容

        for (i=0; i<secremain; i++)     // 校验数据
        {
            if (gFlashBuf[secoff+i] != 0XFFFFFFFF)
            {
                break;      // 需要擦除
            }
        }

        if (i < secremain)  // 需要擦除
        {
            fmc_page_erase(secpos * GD_SECTOR_SIZE + GD32_FLASH_BASE_ADR);

            for (i=0; i <secremain; i++)        // 复制
            {
                gFlashBuf[i+secoff] = buf[i];
            }

            FLASH_WriteNoCheck(secpos * GD_SECTOR_SIZE + GD32_FLASH_BASE_ADR, gFlashBuf, GD_SECTOR_SIZE / 4);     // 写入整个扇区  
        }
        else
        {
            FLASH_WriteNoCheck(adr, buf, secremain);    // 写已经擦除了的,直接写入扇区剩余区间. 		
        }

        if (len == secremain)
        {
            break;      // 写入结束了
        }
        else            // 写入未结束
        {
            secpos++;               // 扇区地址增1
            secoff = 0;             // 偏移位置为0 	 
            buf += secremain;       // 指针偏移
            adr += secremain;       // 写地址偏移	   
            len -=secremain;        // 字节(32位)数递减

            if (len > (GD_SECTOR_SIZE / 4))
            {
                secremain = GD_SECTOR_SIZE / 4;     // 下一个扇区还是写不完
            }
            else 
            {
                secremain = len;                    // 下一个扇区可以写完了
            }
        }	 
    }

    fmc_lock();
}

/**
 * @brief 读四个字节
*/
uint32_t FLASH_ReadWord(uint32_t adr)
{
	return *(volatile uint32_t*)adr; 
}

/**
 * @brief 读FLASH
*/
void FLASH_Read(uint32_t adr, uint32_t *buf, uint16_t len)
{
    uint16_t i = 0;
    
    for (i=0; i<len; i++)
    {
        buf[i] = FLASH_ReadWord(adr);   // 读取4个字节.
        adr += 4;   // 偏移4个字节.
    }
}

void iap_task(void)
{
    uint8_t i = 0;
    uint32_t write_flag = 0, read_flag = 0;

    if (iap_flag)
    {
        iap_flag = 0;

        FLASH_Write(IAP_FLAG_ADR, &write_flag, 1);
        
        for (i = 0; i < 10; i++)
        {
            FLASH_Read(IAP_FLAG_ADR, &read_flag, 1);
            
            if (read_flag != LOAD_APP_FLAG)
            {
                break;
            }
        }
        
        DISABLE_IRQ();
        iap_softreset();
    }
}
